package storage

import (
	"context"
	"fmt"
	"github.com/nuttech/bell"
	"github.com/open-policy-agent/opa/rego"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers"
	"gitverse.ru/synapse/kubelatte/pkg/util/opa"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sync"
)

var Storage *StorageController

type StorageController struct {
	triggers   map[string]v1alpha1.Trigger
	templates  map[string]v1alpha1.Template
	scopes     map[string]v1alpha1.Scope
	namespaces map[string]corev1.Namespace
	regoRules  map[string]rego.PreparedEvalQuery
	mutex      sync.Mutex
	initedFlag bool
	Sync       bool
}

func (r *StorageController) Start(withInit bool, senderMode bool) {
	r.triggers = map[string]v1alpha1.Trigger{}
	r.templates = map[string]v1alpha1.Template{}
	r.scopes = map[string]v1alpha1.Scope{}
	r.namespaces = map[string]corev1.Namespace{}
	r.regoRules = map[string]rego.PreparedEvalQuery{}

	bell.Listen(operator.StorageUpdateTriggerEvent, r.UpdateTrigger)
	bell.Listen(operator.StorageUpdateTemplateEvent, r.UpdateTemplate)
	bell.Listen(operator.StorageUpdateScopeEvent, r.UpdateScope)
	bell.Listen(operator.StorageUpdateNamespaceEvent, r.UpdateNamespace)
	bell.Listen(operator.StorageDeleteTriggerEvent, r.DeleteTrigger)
	bell.Listen(operator.StorageDeleteTemplateEvent, r.DeleteTemplate)
	bell.Listen(operator.StorageDeleteScopeEvent, r.DeleteScope)
	bell.Listen(operator.StorageDeleteNamespaceEvent, r.DeleteNamespace)
	if withInit {
		r.InitStorage()
	}
	r.initedFlag = true
	logs.Debugf("StorageController: Start")
}

func (r *StorageController) Stop() {
	bell.Remove(operator.StorageUpdateTriggerEvent)
	bell.Remove(operator.StorageUpdateTemplateEvent)
	bell.Remove(operator.StorageUpdateScopeEvent)
	bell.Remove(operator.StorageUpdateNamespaceEvent)
	bell.Remove(operator.StorageDeleteTriggerEvent)
	bell.Remove(operator.StorageDeleteTemplateEvent)
	bell.Remove(operator.StorageDeleteScopeEvent)
	bell.Remove(operator.StorageDeleteNamespaceEvent)
	logs.Debug("StorageController: Stop")
}

func (r *StorageController) InitStorage() {
	r.mutex.Lock()
	ctx := context.Background()
	if operator.IsControllerNeeded("trigger") {
		//fix read from global cluster
		list, err := operator.Cl.Trigger(operator.OperatorNamespace).List(ctx, metav1.ListOptions{})
		if err != nil {
			logs.Errorf("[StorageController] Failed initialize %s", err)
			return
		}
		for _, trigger := range list.Items {
			r.triggers[trigger.GetNamespacedName()] = trigger
		}
	}
	if operator.IsControllerNeeded("template") {
		//fix read from global cluster
		tms, err := operator.Cl.Template(operator.OperatorNamespace).List(ctx, metav1.ListOptions{})
		if err != nil {
			logs.Errorf("[StorageController] Failed initialize %s", err)
			return
		}
		for _, tm := range tms.Items {
			r.templates[tm.GetNamespacedName()] = tm
		}
	}

	if operator.IsControllerNeeded("scope") {
		ss, err := operator.Cl.Scope(operator.OperatorNamespace).List(ctx, metav1.ListOptions{})
		if err != nil {
			logs.Errorf("[StorageController] Failed initialize %s", err)
			return
		}
		for _, s := range ss.Items {
			r.scopes[s.GetNamespacedName()] = s
		}
	}
	r.mutex.Unlock()
}

func (r *StorageController) UpdateTrigger(message bell.Message) {
	r.mutex.Lock()
	trigger := message.Value.(v1alpha1.Trigger)
	r.triggers[trigger.GetNamespacedName()] = trigger
	r.mutex.Unlock()
}

func (r *StorageController) UpdateTemplate(message bell.Message) {
	r.mutex.Lock()
	t := message.Value.(v1alpha1.Template)
	r.templates[t.GetNamespacedName()] = t
	r.mutex.Unlock()

	if t.Spec.Type == "rego" {
		r.UpdateRegoRule(t.GetNamespacedName(), t.Spec)
	} else {
		_, ok := r.regoRules[t.GetNamespacedName()]
		if ok {
			r.DeleteRegoRule(t.GetNamespacedName())
		}
	}

}

func (r *StorageController) UpdateRegoRule(namespacedName string, ts v1alpha1.TemplateSpec) {
	precompileRule, _ := opa.Precompile(ts.Data)
	r.mutex.Lock()
	r.regoRules[namespacedName] = precompileRule
	r.mutex.Unlock()
}

func (r *StorageController) UpdateScope(message bell.Message) {
	r.mutex.Lock()
	t, ok := message.Value.(v1alpha1.Scope)
	if !ok {
		logs.Warn("can not convert received scope to v1alpha1.Scope")
	}
	r.scopes[t.GetNamespacedName()] = t
	r.mutex.Unlock()
}

func (r *StorageController) UpdateNamespace(message bell.Message) {
	r.mutex.Lock()
	t := message.Value.(corev1.Namespace)
	ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: t.ObjectMeta.Name,
		Namespace: t.ObjectMeta.Namespace, Labels: t.ObjectMeta.Labels,
	}}
	r.namespaces[t.ObjectMeta.Name] = ns
	r.mutex.Unlock()
}

func (r *StorageController) DeleteTrigger(message bell.Message) {
	r.mutex.Lock()
	trigger := message.Value.(v1alpha1.Trigger)
	delete(r.triggers, trigger.GetNamespacedName())
	r.mutex.Unlock()
}

func (r *StorageController) DeleteTemplate(message bell.Message) {
	r.mutex.Lock()
	t := message.Value.(v1alpha1.Template)
	delete(r.templates, t.GetNamespacedName())
	r.mutex.Unlock()

	_, ok := r.regoRules[t.GetNamespacedName()]
	if ok {
		r.DeleteRegoRule(t.GetNamespacedName())
	}
}

func (r *StorageController) DeleteRegoRule(namespacedName string) {
	r.mutex.Lock()
	delete(r.regoRules, namespacedName)
	r.mutex.Unlock()
}

func (r *StorageController) DeleteScope(message bell.Message) {
	r.mutex.Lock()
	t := message.Value.(v1alpha1.Scope)
	delete(r.scopes, t.GetNamespacedName())
	r.mutex.Unlock()
}

func (r *StorageController) DeleteNamespace(message bell.Message) {
	r.mutex.Lock()
	t := message.Value.(corev1.Namespace)
	delete(r.namespaces, t.ObjectMeta.Name)
	r.mutex.Unlock()
}

func (r *StorageController) CheckTriggerTemplatePair() {
	for _, trigger := range r.triggers {
		for _, config := range trigger.Spec.CreationConfigs {
			for _, ref := range config.TemplateRefs {
				_, ok := r.templates[ref]
				if !ok {
					logs.Error(fmt.Sprintf("StorageController: CheckTriggerTemplatePair Trigger[%s] contains invalid template ref %s", trigger.Name, ref))
				}
			}
		}
	}
}

func (r *StorageController) GetScopes() map[string]v1alpha1.Scope {
	defer r.mutex.Unlock()
	r.mutex.Lock()
	return r.scopes
}

func (r *StorageController) GetTemplates() map[string]v1alpha1.Template {
	defer r.mutex.Unlock()
	r.mutex.Lock()
	return r.templates
}

func (r *StorageController) GetRegoRules() map[string]rego.PreparedEvalQuery {
	defer r.mutex.Unlock()
	r.mutex.Lock()
	return r.regoRules
}

func (r *StorageController) GetTriggers() map[string]v1alpha1.Trigger {
	defer r.mutex.Unlock()
	r.mutex.Lock()
	return r.triggers
}

func (r *StorageController) GetNamespaces() map[string]corev1.Namespace {
	defer r.mutex.Unlock()
	r.mutex.Lock()
	return r.namespaces
}

func (r *StorageController) GetScopeItems(scType string) []v1alpha1.Item {
	defer r.mutex.Unlock()
	r.mutex.Lock()
	var result []v1alpha1.Item
	for _, val := range r.scopes {
		if val.Spec.Type == scType {
			result = append(result, val.Spec.Items...)
		}
	}

	return result
}

func (r *StorageController) HealthCheck() bool {
	return r.initedFlag
}

func CheckUnhandledTIs() {
	if !operator.IsCreatorRole() {
		return
	}
	nss := Storage.GetNamespaces()
	for _, ns := range nss {
		if controllers.CheckNamespace(ns.ObjectMeta.Name, operator.NSSelectorTI) {
			controllers.HandleUnhandledTIs(ns.ObjectMeta.Name)
		}
	}
}
